package com.shiro.filter;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.common.ActionResult;
import com.common.Const;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;


public class SimpleFormAuthenticationFilter extends FormAuthenticationFilter{
	
	private static Logger logger = LoggerFactory.getLogger(SimpleFormAuthenticationFilter.class);
	
	/**
	 * 重写onLoginSuccess,处理登录成功的逻辑
	 * 逻辑：
	 * 1、若为不ajax请求,执行重定向
	 * 2、若为ajax请求,返回JSON,提示用户登录成功
	 * @author Eason
	 */
	@Override
	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
            ServletRequest request, ServletResponse response) throws Exception {
		logger.info("[authc:onLoginSuccess]");
	    
		// Ajax请求
    	HttpServletRequest httpServletRequest = (HttpServletRequest) request; 
	    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
	
	    if (!"XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With"))) {// 不是ajax请求
	    	//super.onLoginSuccess(token, subject, httpServletRequest, httpServletResponse);
	    	issueSuccessRedirect(request, response);
	    
	    }else {
	    	// 响应请求,返回json字符串
	    	httpServletResponse.setContentType("application/json");
	    	httpServletResponse.setCharacterEncoding("UTF-8");
	        PrintWriter out = httpServletResponse.getWriter();
	        
	        ActionResult actionResult = new ActionResult("", Const._INT_TRUE, "", "登录成功", null);
	        ObjectMapper mapper = new ObjectMapper();
	        String json = mapper.writeValueAsString(actionResult);
	        
	        out.println(json);
	        
	        out.flush();
	        out.close();
	    } 

		return false;

	}

	/**
	 * 重写onLoginFailure,处理登录失败的逻辑
	 * 逻辑：
	 * 1、若为不ajax请求,setFailureAttribute,可以用LoginController的fail方法中获取到
	 * 2、若为ajax请求,返回JSON,提示用户登录失败及错误类型
	 * @author Eason
	 */
    @Override 
    protected boolean onLoginFailure(AuthenticationToken token,AuthenticationException e, ServletRequest request,ServletResponse response) {
	    logger.info("onLoginFailure");
    	if (!"XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {// 不是ajax请求
		    setFailureAttribute(request, e);
		    logger.info(e.getMessage());
		    //e.printStackTrace();
		    return true;
	    } 
	    try {
	    	// 响应请求,返回json字符串
	    	response.setContentType("application/json");
	    	response.setCharacterEncoding("UTF-8");
	        PrintWriter out = response.getWriter();
	        String message = e.getClass().getSimpleName();
	        logger.info(" e.getClass().getSimpleName():"+ e.getClass().getSimpleName());
	        ActionResult actionResult = new ActionResult("", Const._INT_FALSE, Const._ERROR_CODE_UNIDENTIFIED, "原因不明", null);
	        if ("IncorrectCredentialsException".equals(message)) {
	        	  actionResult = new ActionResult("", Const._INT_FALSE, Const._ERROR_CODE_UNIDENTIFIED, "帐号或密码错误", null);
		    } else if ("UnknownAccountException".equals(message)) {
		    	 actionResult = new ActionResult("", Const._INT_FALSE, Const._ERROR_CODE_UNIDENTIFIED, "帐号不存在", null);
		    } else if ("LockedAccountException".equals(message)) {
		    	 actionResult = new ActionResult("", Const._INT_FALSE, Const._ERROR_CODE_UNIDENTIFIED, "帐号被锁定", null);
		    } else {
		    	 actionResult = new ActionResult("", Const._INT_FALSE, Const._ERROR_CODE_UNIDENTIFIED, "未知错误", null);
		    }
	        ObjectMapper mapper = new ObjectMapper();
	        String json = mapper.writeValueAsString(actionResult);
	        
	        out.println(json);
	        
	        out.flush();
	        out.close();
		    } catch (IOException e1) {
		    // TODO Auto-generated catch block
		    e1.printStackTrace();
		    } 
	    return false; 
    }

	
	/**
	 * 重写onAccessDenied,扩展异步请求认证提示功能
	 * 逻辑：
	 * 1、若为登录请求,有以下几种情况.
	 * (1)若为POST登录请求时(Normal),调用applicationContext-shiro.xml中指定的loginUrl='/login',POST用户名密码,执行登录操作.
	 * (2)若为POST登录请求时(AJAX),ajax登录后需要调用onLoginSuccess和onLoginFailure处理
	 * (3)若为GET登录请求时(调用'/login',访问login.jsp登录页面),允许访问login.jsp页面
	 * 
	 * 2、若不为登录请求,受到authc的认证限制,若为AJAX请求则以JSON方式返回失败信息,Normal请求跳转到登录页面
	 * 
	 * @author Eason
	 */
	@Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    	
    	logger.info("authc filter onAccessDenied");
    	
    	HttpServletRequest httpRequest = (HttpServletRequest) request;
    	HttpServletResponse httpResponse = (HttpServletResponse) response;

    	// 若为登录请求,包含:(1)POST登录请求 (2)访问login
    	if (isLoginRequest(request, response)) {
    		logger.info("Login Request");
    		// 若为登录请求(post)
            if (isLoginSubmission(request, response)) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Login submission detected.  Attempting to execute login.");
                }
                // 执行登录操作
                return executeLogin(request, response);
            } else {
            	// 访问 /login 控制器
            	
                if (logger.isTraceEnabled()) {
                    logger.trace("Login page view.");
                }
                //allow them to see the login page ;) 
                return true;
            }
        } else {
    		
        	logger.info("Not Login Request");
        	
        	//非登录请求,如未登陆时购买商品
        	if (logger.isTraceEnabled()) {
                logger.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
        	
        	// 若为AJAX请求,返回JSON
        	if ("XMLHttpRequest".equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))){
        		
        		// 记录登录前操作受限的url地址,以便登录后再次跳转到该地址(注意：若为POST请求则无法跳转)
        		logger.info("AJAX Request");
        		saveRequest(request);
        		
        		// 响应请求,返回json字符串
        		httpResponse.setContentType("application/json");
        		httpResponse.setCharacterEncoding("UTF-8");
    	        PrintWriter out = httpResponse.getWriter();
    	        
    	        ActionResult actionResult = new ActionResult("", Const._INT_FALSE, Const._ERROR_CODE_UNIDENTIFIED, Const._FAIL_UNIDENTIFIED, null);
    	        ObjectMapper mapper = new ObjectMapper();
    	        String json = mapper.writeValueAsString(actionResult);
    	        
    	        out.println(json);
    	        
    	        out.flush();
    	        out.close();
        		
        	}else{
        		logger.info("Normal Request");
        		// 若不为AJAX请求则跳转至登录页面
        		saveRequestAndRedirectToLogin(request, response);
        	}
            
            return false;
        }
    }
    
    /**
     * isAccessAllowed决定是否允许用户的登陆
     * 逻辑：
     * 1、在这里可以解决以"记住我"方式登录而带来的问题
     * 2、目前没有任何实现
     * 
     * @author Eason
     */
    
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    }
    
    /**
     * 重写excuteLogin方法,以代码方式扩展"记住我"功能.(目前在applicationContext-shiro.xml中通过配置使用该功能)
     * 逻辑：
     * 1、根据request获取login页面传的name="remember"参数,设置remeberMe=true/false
     * 2、将AuthenticationToken强制转换为UsernamePasswordToken,以便setRememberMe();
     * 3、subject.login()传入参数改为强制转换后的UsernamePasswordToken
     * 
     * @author Eason
     
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        
    	// 登录页面勾选"记住我",提交name=remember,值为on;不勾选则为null
    	Boolean rememberMe = false;
    	if(request.getParameter("remember")!=null){
    		rememberMe = true;
    	}

    	AuthenticationToken token = createToken(request, response);
        
    	//设置记住我需要将AuthenticationToken转换为UserNamePasswordToken
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token; 
        if(rememberMe){
        	usernamePasswordToken.setRememberMe(true);
        }
        
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(usernamePasswordToken);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }
    */
}
